Computing technology has revolutionized the way people work and play and has contributed enormously to the advancement of humankind. Computers now aid in enumerable applications such as word processing, computer simulations, advanced gaming, voice recognition, and much more. Computing systems now come in a wide-variety of forms including, for example, desktop computers, laptop computers, Personal Digital Assistants (PDAs), and even mobile telephones and devices.
The various computing systems process or execute computer applications in many ways using a wide variety of processing tools and components. For example, computing systems commonly use a function or call stack as a data area tool, which provide applications with the ability for storing requests, functions, intermediate results, or other data awaiting processing. They are considered a stack because when one function calls another, rather than simply jumping to another part of the program, the current or return address in the call function is pushed onto the stack. Its value is then used when the callee function terminates by jumping the program counter back to the value that was stored there.
Computing systems allocate the data size of buffers on the stack to some finite amount of data. Due to the limited data size of these stack buffers, an anomalous condition can occur where a program somehow writes data beyond the allocated end of a stack buffer (commonly referred to as a stack buffer overrun or overflow). As a result, the extra information—which has to go somewhere—can overflow into adjacent data storage, corrupting or overwriting the valid data held therein. Although it may occur accidentally through programming error, buffer overflow is an increasingly common type of security attack on data integrity. In buffer overflow attacks, an overflow can corrupt protected data (return addresses, security objects, exception handling objects, other code addresses or data values) in a way designed to trigger specific actions, in effect causing new instructions to be executed on the attacked computer that could, e.g., damage the user's files, change data, and/or disclose confidential information.
Because such attacks (and buffer overruns in general) have the potential to cause serious problems to a computing system, different mechanisms for detecting and preventing buffer overrun attacks have been developed. For example, one common stack protection places a security cookie (or canary) on the stack in front of the saved protected data when calling a function. Such processes take advantage of the fact that functions must write data on the stack data in a predefined order (e.g., bottom to top, left to right, etc.). Accordingly, when an overflow to a local buffer occurs, the cookie is overwritten on the way to overwriting the saved protected data. Before the function returns, the cookie is checked against an authoritative version of the cookie (often referred to as a “global cookie” value, which may be randomly generated during routine startup) typically stored in a more secure section of the module where the function resides. If the cookies do not match then it is assumed that the buffer has been overflowed and the process may be aborted and/or other appropriate action taken.
Although the above described buffer protection mechanisms work well in many situations, they do not protect against all forms of attacks. For example, because the cookie value is checked only upon return to the operation that called the function, there is a possibility that the stack corruption may be exploited before the function returns. For instance, when a problem occurs such as a memory access violation or other error, an exception handler can be called to deal with such situations and allow the process to recover and continue execution as normal. In such instance, a scan of the stack is performed (referred to herein as a stack “walk” or “crawling” or stack unwind) to find the appropriate the exception handler. If, however, an attacker has overwritten this portion of the stack, the attacker can control the stack walk, and control the return address of the exception handling function, which may point to a block of code or an instruction that will redirect the flow of execution to some unexpected location (e.g., back to the overwritten buffer) allowing the attacker to gain control of the process. In addition, because the exception handler does not have information about where cookie(s) may be located on the stack (since typically it's only the callee function that has this information), the cookies cannot be checked during the stack-walk.
There are also other scenarios where a stack-walk is invoked and uses data on the stack which if corrupted can lead to an exploit before the stack cookie is checked when a function returns. For example, automatic memory management routines, such as garbage collecting (also known as GC), crawl the stack in an attempt to find the objects being used by the program, and to reclaim the memory used by objects that will never be accessed again by the application. In addition, there may be security functions that inquire about other functions that have called a particular routine in order to determine the rights of all corresponding functions. As before, the initiation of the before the return function is called; and thus before the cookie is checked.
Another problem with current buffer overrun detection systems is that even when the cookie is determined valid, a race condition may exists between the time the cookie is determined valid and when the protected stack data is used. For example, a lot of applications today are “multithreaded”, which means that they can execute more than one thread or task at a time. Accordingly, problems may occur when two threads are reading and/or modifying the same stack at the same time. For instance, imagine two threads, A and B, who are operating on the same portion of the stack at virtually the same time. For example, thread A might want to discover all the functions being executed by thread B. Assuming thread A is a callee function that discovers a return value, it loads the appropriate cookie value from memory for validation. While thread A authenticates the cookie, however, thread B steps in and overwrites the protected data the cookie was protecting. After validation of the cookie, thread A now comes back and uses the overwritten data, allowing thread B to gain control of the process. Note that since thread B needs to get the sequence of events in just the right order, race conditions are very rare. Nevertheless, intruders are willing to continually attempt this technique in order to hack into computing systems.